/*
 * $Id: TemplateLogRecordFormatter.java 134 2013-01-23 07:30:03Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/tags/release-20130123/src/java/com/google/code/ultralog/writer/TemplateLogRecordFormatter.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.writer;

import java.lang.reflect.Field;

import com.google.code.ultralog.formatter.FormatterFactory;
import com.google.code.ultralog.formatter.IntegerFormatter;
import com.google.code.ultralog.formatter.LongFormatter;
import com.google.code.ultralog.formatter.ReferenceFormatter;
import com.google.code.ultralog.template.Template;
import com.google.code.ultralog.template.Template.Placeholder;
import com.google.code.ultralog.utils.CharacterOutputStream;
import com.google.code.ultralog.utils.CharacterSequence;

/**
 * Implementation of {@link LogRecordFormatter} interface that formats log
 * records according to given template.  Template could contain placeholders
 * with the following names:
 *
 * <table border="1">
 *   <tr>
 *     <th>Name</th>
 *     <th>Type</th>
 *     <th>Description</th>
 *   </tr>
 *   <tr>
 *     <td>logger</td>
 *     <td>String</td>
 *     <td>Name of the logger</td>
 *   </tr>
 *   <tr>
 *     <td>levelName</td>
 *     <td>String</td>
 *     <td>Name of the logging level</td>
 *   </tr>
 *   <tr>
 *     <td>level</td>
 *     <td>int</td>
 *     <td>Logging level</td>
 *   </tr>
 *   <tr>
 *     <td>message</td>
 *     <td>CharacterSequence</td>
 *     <td>Log message</td>
 *   </tr>
 *   <tr>
 *     <td>threadName</td>
 *     <td>String</td>
 *     <td>Thread name</td>
 *   </tr>
 *   <tr>
 *     <td>threadID</td>
 *     <td>long</td>
 *     <td>Thread ID</td>
 *   </tr>
 *   <tr>
 *     <td>timestamp</td>
 *     <td>long</td>
 *     <td>
 *       Time stamp in milliseconds as returned by
 *       {@link System#currentTimeMillis()}
 *     </td>
 *   </tr>
 * </table>
 *
 * @author Mikhail Vladimirov
 */
public class TemplateLogRecordFormatter implements LogRecordFormatter
{
    private final LogRecordFormatter [] chunks;

    /**
     * Create new template log record formatter that formats log records
     * according to given template and uses given formatter factory.
     *
     * @param template template to format log records according to
     * @param formatterFactory formatter factory to use
     */
    public TemplateLogRecordFormatter (
        Template template, FormatterFactory formatterFactory)
    {
        if (template == null)
            throw new IllegalArgumentException ("Template is null");

        if (formatterFactory == null)
            throw new IllegalArgumentException ("Formatter factory is null");

        int count = template.getPlaceholderCount ();
        chunks = new LogRecordFormatter [count * 2 + 1];

        for (int i = 0; i <= count; i++)
            chunks [i * 2] = new LiteralChunk (template.getLiteralAt (i));

        for (int i = 0; i < count; i++)
        {
            Placeholder placeholder = template.getPlaceholderAt (i);

            String name = placeholder.getName ();
            String options = placeholder.getOptions ();

            LogRecordFormatter chunk;

            if ("logger".equals (name))
                chunk = new LoggerNameChunk (
                    formatterFactory.createReferenceFormatter (
                        String.class, options));
            else if ("levelName".equals (name))
                chunk = new LevelNameChunk (
                        formatterFactory.createReferenceFormatter (
                            String.class, options));
            else if ("level".equals (name))
                chunk = new LevelChunk (
                        formatterFactory.createIntegerFormatter (
                            options));
            else if ("message".equals (name))
                chunk = new MessageChunk (
                        formatterFactory.createReferenceFormatter (
                            CharacterSequence.class, options));
            else if ("threadName".equals (name))
                chunk = new ThreadNameChunk (
                        formatterFactory.createReferenceFormatter (
                            String.class, options));
            else if ("threadID".equals (name))
                chunk = new ThreadIDChunk (
                        formatterFactory.createLongFormatter (
                            options));
            else if ("timestamp".equals (name))
                chunk = new TimestampChunk (
                        formatterFactory.createLongFormatter (
                            options));
            else throw new IllegalArgumentException (
                "Unsupported placeholder name: " + name);

            chunks [i * 2 + 1] = chunk;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void formatLogRecord (String loggerName, int level,
        String levelName, CharacterSequence message,
        CharacterOutputStream destination) {
        for (int count = chunks.length, i = 0; i < count; i++)
            chunks [i].formatLogRecord (
                loggerName, level, levelName, message, destination);
    }

    private static class LiteralChunk implements LogRecordFormatter
    {
        private final String literal;

        public LiteralChunk (String literal)
        {
            if (literal == null)
                throw new IllegalArgumentException ("Literal is null");

            this.literal = literal;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            destination.write (literal, 0, literal.length ());
        }
    }

    private static class LoggerNameChunk implements LogRecordFormatter
    {
        private final ReferenceFormatter <? super String> formatter;

        public LoggerNameChunk (ReferenceFormatter <? super String> formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            formatter.format (loggerName, destination);
        }
    }

    private static class LevelChunk implements LogRecordFormatter
    {
        private final IntegerFormatter formatter;

        public LevelChunk (IntegerFormatter formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            formatter.format (level, destination);
        }
    }

    private static class LevelNameChunk implements LogRecordFormatter
    {
        private final ReferenceFormatter <? super String> formatter;

        public LevelNameChunk (ReferenceFormatter <? super String> formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            formatter.format (levelName, destination);
        }
    }

    private static class MessageChunk implements LogRecordFormatter
    {
        private final ReferenceFormatter <? super CharacterSequence> formatter;

        public MessageChunk (
            ReferenceFormatter <? super CharacterSequence> formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            formatter.format (message, destination);
        }
    }

    private static class ThreadNameChunk implements LogRecordFormatter
    {
        private final static Field THREAD_NAME_FIELD;

        private final static ThreadLocal <StringCharArrayPair> THREAD_NAME;

        static
        {
            Field threadNameField = null;

            try
            {
                threadNameField = Thread.class.getDeclaredField ("name");
                if (!char [].class.equals (threadNameField.getType ()))
                    threadNameField = null;
                else
                {
                    threadNameField.setAccessible (true);
                }
            }
            catch (Exception ex)
            {
                // Ignore
            }

            THREAD_NAME_FIELD = threadNameField;
            if (threadNameField != null)
                THREAD_NAME = new ThreadLocal <StringCharArrayPair> ();
            else THREAD_NAME = null;
        }

        private final ReferenceFormatter <? super String> formatter;

        public ThreadNameChunk (ReferenceFormatter <? super String> formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            String threadName = null;
            char [] threadNameChars = null;

            if (THREAD_NAME_FIELD != null)
            {
                StringCharArrayPair pair = THREAD_NAME.get ();

                if (pair != null)
                {
                    try
                    {
                        threadNameChars =
                            (char [])THREAD_NAME_FIELD.get (
                                Thread.currentThread ());

                        if (pair.charArray == threadNameChars)
                            threadName = pair.string;
                    }
                    catch (Exception ex)
                    {
                        // Ignore
                    }
                }
            }

            if (threadName == null)
            {
                threadName = Thread.currentThread ().getName ();
                if (THREAD_NAME != null)
                    THREAD_NAME.set (
                        new StringCharArrayPair (threadName, threadNameChars));
            }

            formatter.format (threadName, destination);
        }

        private static class StringCharArrayPair
        {
            public final String string;
            public final char [] charArray;

            public StringCharArrayPair (String string, char [] charArray)
            {
                this.string = string;
                this.charArray = charArray;
            }
        }
    }

    private static class ThreadIDChunk implements LogRecordFormatter
    {
        private final LongFormatter formatter;

        public ThreadIDChunk (LongFormatter formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            formatter.format (Thread.currentThread ().getId (), destination);
        }
    }

    private static class TimestampChunk implements LogRecordFormatter
    {
        private final LongFormatter formatter;

        public TimestampChunk (LongFormatter formatter)
        {
            if (formatter == null)
                throw new IllegalArgumentException ("Formatter is null");

            this.formatter = formatter;
        }

        @Override
        public void formatLogRecord (String loggerName, int level,
            String levelName, CharacterSequence message,
            CharacterOutputStream destination) {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (levelName == null)
                throw new IllegalArgumentException ("Level name is null");

            if (message == null)
                throw new IllegalArgumentException ("Message is null");

            if (destination == null)
                throw new IllegalArgumentException ("Destination is null");

            formatter.format (System.currentTimeMillis (), destination);
        }
    }
}
